#include <bits/stdc++.h>
#include <winsock2.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>

#pragma comment(lib, "WS2_32.lib")
using namespace std;

#define PRINTF(str) printf("[%s-%d]" #str "=%s\n", __func__, __LINE__, str); //?

void error_die(const char *str)
{
    perror(str);
    exit(1);
}

int startup(unsigned short *port)
{
    // 1.网络通信的初始化
    WSADATA data;
    int ret = WSAStartup(MAKEWORD(1, 1), &data);
    if (ret != 0) // 初始化失败
    {
        error_die("网络通信初始化失败");
    }

    // 2.创建套接字
    int server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server_socket == -1)
    {
        error_die("套接字创建失败");
    }

    // 3.设置套接字属性端口可复用(可略),保证端口关闭后立马可以重新使用
    int opt = 1;
    ret = setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
    if (ret == -1)
    {
        error_die("端口可复用设置失败");
    }

    // 4.把套接字和本地的IP地址和端口绑定起来
    sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(*port);
    server_addr.sin_addr.s_addr = htonl(0); // 表示所有人都可以访问
    ret = bind(server_socket, (sockaddr *)&server_addr, sizeof(server_addr));
    if (ret == -1)
    {
        error_die("bind失败");
    }

    // 动态分配一个端口
    int namelen = sizeof(server_addr);
    if (*port == 0)
    {
        ret = getsockname(server_socket, (sockaddr *)&server_addr, &namelen);
        if (ret == -1)
        {
            error_die("动态分配端口失败");
        }
        *port = server_addr.sin_port;
    }

    // 5.套接字转化为监听套接字
    ret = listen(server_socket, 5);
    if (ret == -1)
    {
        error_die("套接字转化为监听套接字失败");
    }

    return server_socket;
}

// 从指定的客户端套接字读取一行数据(遇到回车符为止)
int get_line(int sock, char *buff, int size)
{
    char c = 0;
    int i = 0;
    while (i < size - 1 && c != '\n')
    {
        int n = recv(sock, &c, 1, 0);
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(sock, &c, 1, MSG_PEEK); //?
                if (n > 0 && c == '\n')
                {
                    recv(sock, &c, 1, 0);
                }
                else
                {
                    c = '\n';
                }
            }
            buff[i++] = c;
        }
        else
        {
            c = '\n';
        }
    }
    buff[i] = 0; //'\0'的ASCII是0
    return 0;
}

void unimplement(int client) // 向指定的套接字，发送一个提示还没有实现的错误页面
{
}

void cat(int client, FILE *resource) // 发送图片资源
{
    char buff[4096];
    int count = 0;
    while (true)
    {
        int ret = fread(buff, sizeof(char), sizeof(buff), resource);
        if (ret <= 0)
        {
            break;
        }
        send(client, buff, ret, 0);
        count = count + ret;
    }
    printf("一共发送%d字节也给浏览器\n", count);
}

void not_found(int client) // 没有
{
    char buff[1024];

    // 状态行
    strcpy(buff, "HTTP/1.1 404 Not Found\r\n");
    send(client, buff, strlen(buff), 0);

    // 服务器信息
    strcpy(buff, "Server: xujiawen/0.1\r\n");
    send(client, buff, strlen(buff), 0);

    // 内容类型
    strcpy(buff, "Content-Type: text/html\r\n");
    send(client, buff, strlen(buff), 0);

    // 空行结束头部
    strcpy(buff, "\r\n");
    send(client, buff, strlen(buff), 0);

    // 尝试打开404页面
    FILE *resource = fopen("htdocs/nofound.html", "r");
    if (resource)
    {
        cat(client, resource);
        fclose(resource);
    }
    else
    {
        // 如果404页面也不存在，发送默认提示
        const char *default_msg = "<html><body><h1>404 Not Found</h1><p>The requested resource was not found on this server.</p></body></html>";
        send(client, default_msg, strlen(default_msg), 0);
    }
}

void headers(int client, const char *content_type) // 发送响应包的头信息
{
    char buff[1024];
    // 发送响应头的第一行:状态行
    strcpy(buff, "HTTP/1.1 200 OK\r\n");
    send(client, buff, strlen(buff), 0);

    // 发送响应头的第二行:消息头部
    strcpy(buff, "Server: xujiawen/0.1\r\n");
    send(client, buff, strlen(buff), 0);

    sprintf(buff, "Content-Type: %s\r\n", content_type); // 动态设置内容类型
    send(client, buff, strlen(buff), 0);

    strcpy(buff, "\r\n");
    send(client, buff, strlen(buff), 0);
}

void server_file(int client, const char *filename) // 把浏览器请求的文件发送给浏览器
{
    int numchars = 1; //?
    char buff[1024];
    while (numchars > 0 && strcmp(buff, "\n")) // 依然需要把请求数据包的剩余部分全部读完
    {
        numchars = get_line(client, buff, sizeof(buff));
        PRINTF(buff);
    }

    const char *content_type = "text/plain";
    const char *dot = strrchr(filename, '.');
    if (dot != NULL)
    {
        if (_stricmp(dot, ".html") == 0 || _stricmp(dot, ".htm") == 0)
        {
            content_type = "text/html";
        }
        else if (_stricmp(dot, ".png") == 0)
        {
            content_type = "image/png";
        }
        else if (_stricmp(dot, ".jpg") == 0 || _stricmp(dot, ".jpeg") == 0)
        {
            content_type = "image/jpeg";
        }
        else if (_stricmp(dot, ".gif") == 0)
        {
            content_type = "image/gif";
        }
    }

    FILE *resource = fopen(filename, "rb");
    if (resource == NULL) // 打开失败
    {
        not_found(client);
    }
    else // 正式发送资源给浏览器
    {
        headers(client, content_type); // 发送响应头

        cat(client, resource); // 发送请求的资源信息

        printf("文件资源发送成功!\n");
    }

    fclose(resource);
}

// 处理用户请求的线程函数
DWORD WINAPI accept_request(LPVOID arg)
{
    // 读取一行数据
    char buff[1024];
    int client = (SOCKET)arg;
    int numchars = get_line(client, buff, sizeof(buff));
    PRINTF(buff);

    // 获取HTTP请求头的类型:GET或POST
    char method[255]; // 存放请求头的类型
    int j = 0, i = 0;
    while (!isspace(buff[j]) && i < sizeof(method) - 1)
    {
        method[i++] = buff[j++];
    }
    method[i] = 0;
    PRINTF(method);

    // 检查请求的类型(方法),本服务器是否支持
    if (stricmp(method, "GET") && stricmp(method, "POST"))
    {
        unimplement(client);
        return 0;
    }

    // 获取浏览器请求的资源文件的路径
    char url[255]; // 存放请求的资源的完整路径
    i = 0;
    while (isspace(buff[j]) && j < sizeof(buff))
    {
        j++;
    }
    while (!isspace(buff[j]) && i < sizeof(url) - 1 && j < sizeof(buff))
    {
        url[i++] = buff[j++];
    }
    url[i] = 0;
    PRINTF(url);

    char path[512] = "";
    sprintf(path, "htdocs%s", url);
    if (path[strlen(path) - 1] == '/')
    {
        strcat(path, "index.html");
    }
    PRINTF(path);

    struct stat status;            //?
    if (stat(path, &status) == -1) // 访问失败
    {

        // 读取剩余的浏览器的请求数据
        while (numchars > 0 && strcmp(buff, "\n"))
        {
            numchars = get_line(client, buff, sizeof(buff));
        }
        not_found(client);
    }
    else // 访问成功
    {
        if ((status.st_mode & S_IFMT) == S_IFDIR) // 如果是目录的话,还要再进行拼接,默认访问该目录下的index.html
        {
            strcat(path, "/index.html");
        }

        server_file(client, path);
    }
    closesocket(client);
    return 0;
}

int main()
{
    unsigned short port = 80;
    int server_sock = startup(&port);
    cout << "服务启动!正在监听" << port << "端口" << endl;
    // cout << server_sock << endl;

    sockaddr_in client_addr;
    int client_addr_len = sizeof(client_addr);
    while (true)
    {
        // 阻塞式等待用户通过浏览器发起访问(只如果没有人访问时，会一直在这里等待)

        SOCKET client_sock = accept(server_sock, (sockaddr *)&client_addr, &client_addr_len);
        if (client_sock == -1)
        {
            error_die("accept失败");
        }

        DWORD threadid = 0;
        CreateThread(0, 0, accept_request, (void *)client_sock, 0, &threadid);
    }

    closesocket(server_sock);
    return 0;
}